// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_particle_emitter.h"
#include "mk_particle_pool.h"
#include "mk_particle_common.h"
#include "mk_error.h"
#include "mk_engine.h"
#include "mk_particle_pool.h"
#include "mk_particle_renderer.h"
#include "mk_math.h"
#include "mk_particle_modifier_color.h"
#include "mk_particle_modifier_size.h"
#include "mk_particle_modifier_direction_force.h"
#include "mk_particle_modifier_texture_frame.h"

namespace MK3DE
{
	CParticleEmitter::CParticleEmitter() : 
		// Data initialize.
		CountMin(1), CountMax(1),
		ScatterMin(0), ScatterMax(0),
		RotationMin(0), RotationMax(0),
		LifeMin(2000), LifeMax(2000),
		SpeedMin(20), SpeedMax(20),
		Color(0xffffffff),
		ParticleSizeMin(20, 20), ParticleSizeMax(20, 20),
		SameWidthHeight(TRUE),
		TextureFrame(0),
		ParticleBlendMode(PRBM_SRC_ALPHA_DST_ONE),
		ShapeType(PEST_RECTANGLE),
		ShapeParam1(0),
		ShapeParam2(0),
		ShapeParam3(0),
		ParticleType(PT_BILLBOARD),

		// something else.
		_remainEmitTime(0)
	{
	}

	CParticleEmitter::~CParticleEmitter()
	{
		__Clean();
	}

	void CParticleEmitter::__Clean()
	{
		for (int i = 0; i < (int)_vecEmitterModifier.size(); ++i)
		{
			CParticleEmitterModifier* pModifier = _vecEmitterModifier[i];
			SAFE_DELETE(pModifier);
		}

		for (int i = 0; i < (int)_vecParticleModifier.size(); ++i)
		{
			CParticleModifier* pModifier = _vecParticleModifier[i];
			SAFE_DELETE(pModifier);
		}

		_vecEmitterModifier.clear();
		_vecParticleModifier.clear();
		_vecParticleIdx.clear();
	}

	BOOL CParticleEmitter::AddEmitterModifier(CParticleEmitterModifier*& pModifier, PARTICLE_EMITTER_MODIFIER_TYPE type)
	{
		switch (type)
		{
		case PEMT_COUNT:
			break;

		default:
			return FALSE;
		}

		_vecEmitterModifier.push_back(pModifier);
		return TRUE;
	}

	CParticleEmitterModifier* CParticleEmitter::GetEmitterModifier(DWORD idx)
	{
		if (idx < (DWORD)_vecEmitterModifier.size())
			return _vecEmitterModifier[idx];
		return NULL;
	}

	BOOL CParticleEmitter::RemoveEmitterModifier(CParticleEmitterModifier* pModifier)
	{
		for (int i = 0; i < (int)_vecEmitterModifier.size(); ++i)
		{
			if (pModifier == _vecEmitterModifier[i])
			{
				SAFE_DELETE(pModifier);
				_vecEmitterModifier.erase(_vecEmitterModifier.begin() + i);
				return TRUE;
			}
		}

		return FALSE;
	}

	BOOL CParticleEmitter::AddParticleModifier(CParticleModifier*& pModifier, PARTICLE_MODIFIER_TYPE type)
	{
		switch (type)
		{
		case PMT_COLOR:
			{
				pModifier = new CParticleModifierColor();
				MK_PROCESS_ERROR(pModifier);
			}
			break;

		case PMT_DIRECTION_FORCE:
			{
				pModifier = new CParticleModifierDirectionForce();
				MK_PROCESS_ERROR(pModifier);
			}
			break;

		case PMT_SIZE:
			{
				pModifier = new CParticleModifierSize();
				MK_PROCESS_ERROR(pModifier);
			}
			break;

		case PMT_TEXTURE_FRAME:
			{
				pModifier = new CParticleModifierTextureFrame();
				MK_PROCESS_ERROR(pModifier);
			}
			break;

		default:
			return FALSE;
		}

		_vecParticleModifier.push_back(pModifier);
		return TRUE;

	Exit0:
		return FALSE;
	}

	CParticleModifier* CParticleEmitter::GetParticleModifier(DWORD idx)
	{
		if (idx < (DWORD)_vecParticleModifier.size())
			return _vecParticleModifier[idx];
		return NULL;
	}

	BOOL CParticleEmitter::RemoveParticleModifier(CParticleModifier* pModifier)
	{
		for (int i = 0; i < (int)_vecParticleModifier.size(); ++i)
		{
			if (_vecParticleModifier[i] == pModifier)
			{
				SAFE_DELETE(pModifier);
				_vecParticleModifier.erase(_vecParticleModifier.begin() + i);
				return TRUE;
			}
		}

		return FALSE;
	}

	BOOL CParticleEmitter::Update(DWORD dt, D3DXMATRIX& mat)
	{
		__EmitParticle(dt, mat);
		__UpdateParticle(dt);
		__ApplyEmitterModifier(dt);
		return TRUE;
	}

	BOOL CParticleEmitter::__ApplyEmitterModifier(DWORD dt)
	{
		return TRUE;
	}

	BOOL CParticleEmitter::__EmitParticle(DWORD dt, D3DXMATRIX& mat)
	{
		DWORD elapseTime = _remainEmitTime + dt;
		float emitCntPerSec = RandomFloat(CountMin, CountMax);
		DWORD dwEmitCnt = (DWORD)((float)elapseTime * 0.001f * emitCntPerSec);
		_remainEmitTime = elapseTime - (DWORD)((float)dwEmitCnt * 1000.0f / emitCntPerSec);

		CParticlePool* particlePool = GetEngine()->_GetParticlePool();
		for (DWORD i = 0; i < dwEmitCnt; ++i)
		{
			Particle* particle = NULL;
			DWORD idx = 0;
			if (!particlePool->AllocateParticle(particle, idx))
				FALSE;

			_vecParticleIdx.push_back(idx);

			float speed = RandomFloat(SpeedMin, SpeedMax);
			float width = RandomFloat(ParticleSizeMin.x, ParticleSizeMax.x);
			float height = width;
			if (!SameWidthHeight)
				height = RandomFloat(ParticleSizeMin.y, ParticleSizeMax.y);

			particle->Life = RandomInt(LifeMin, LifeMax);
			__GetDirection(mat, speed, particle->Velocity);
			particle->Size = D3DXVECTOR2(width, height);
			particle->Color = Color;
			__GetPosition(mat, particle->Position);
			particle->Rotation = RandomFloat(RotationMin, RotationMax);
			particle->TextureFrame = TextureFrame;
			particle->Type = ParticleType;
		}

		return FALSE;
	}

	void CParticleEmitter::__GetDirection(D3DXMATRIX& mat, float speed, D3DXVECTOR3& velocity)
	{
		D3DXVECTOR3 direction;
		if (abs(ScatterMin) < MK_EPSILON && abs(ScatterMax) < MK_EPSILON)
		{
			direction.x = 0;
			direction.y = 1.0f;
			direction.z = 0;
		}
		else
		{
			D3DXMATRIX rotZ, rotY;
			float angleZ = RandomFloat(ScatterMin, ScatterMax);
			float angleY = RandomFloat(-MK_PI, MK_PI);
			D3DXMatrixRotationZ(&rotZ, angleZ);
			D3DXMatrixRotationY(&rotY, angleY);
			D3DXVec3TransformCoord(&direction, &D3DXVECTOR3(0, 100, 0), &rotZ);
			D3DXVec3TransformCoord(&direction, &direction, &rotY);
		}

		D3DXVec3TransformNormal(&direction, &direction, &mat);
		D3DXVec3Normalize(&direction, &direction);
		velocity = direction * speed;
	}

	void CParticleEmitter::__GetPosition(D3DXMATRIX& mat, D3DXVECTOR3& pos)
	{
		D3DXVECTOR3 localPos;

		switch (ShapeType)
		{
		case PEST_RECTANGLE:
			{
				localPos.x = RandomFloat(-ShapeParam1, ShapeParam1);
				localPos.y = RandomFloat(-ShapeParam2, ShapeParam2);
				localPos.z = RandomFloat(-ShapeParam3, ShapeParam3);
			}
			break;

		case PEST_CIRCLE:
			{
				localPos.x = RandomFloat(-ShapeParam1, ShapeParam1);
				localPos.y = RandomFloat(-ShapeParam1, ShapeParam1);
				localPos.z = RandomFloat(-ShapeParam1, ShapeParam1);
			}
			break;

		default:
			{
				localPos.x = 0;
				localPos.y = 0;
				localPos.z = 0;
			}
			break;
		}

		D3DXVec3TransformCoord(&pos, &localPos, &mat);
	}

	BOOL CParticleEmitter::__UpdateParticle(DWORD dt)
	{
		CParticlePool* particlePool = GetEngine()->_GetParticlePool();
		int i = 0;
		while (i != (int)_vecParticleIdx.size())
		{
			DWORD idx = _vecParticleIdx[i];
			Particle* particle = particlePool->GetParticle(idx);
			if (particle == NULL)
			{
				++i;
				continue;
			}

			if (particle->Life <= dt)
			{
				particlePool->DeallocateParticle(idx);
				_vecParticleIdx.erase(_vecParticleIdx.begin() + i);
				continue;
			}

			std::vector<CParticleModifier*>::iterator ipm = _vecParticleModifier.begin();
			while (ipm != _vecParticleModifier.end())
			{
				CParticleModifier* mod = *ipm;
				mod->Apply(particle, dt);
				++ipm;
			}

			particle->Life -= dt;
			particle->Position += (particle->Velocity * (float)dt / 1000.0f);

			++i;
		}

		return TRUE;
	}

	void CParticleEmitter::FillVertexBuffer(DWORD textureCutting)
	{
		CParticlePool* particlePool = GetEngine()->_GetParticlePool();
		CParticleRenderer* particleRenderer = GetEngine()->_GetParticleRenderer();

		DWORD frameCount = textureCutting * textureCutting;
		float fWidth = 1.0f / (float)textureCutting;
		std::vector<DWORD>::iterator i = _vecParticleIdx.begin();
		while (i != _vecParticleIdx.end())
		{
			DWORD idx = *i;
			Particle* p = particlePool->GetParticle(idx);
			if (!p)
				continue;

			particleRenderer->PushParticle(*p, frameCount, textureCutting, fWidth);

			++i;
		}
	}
}
